/*
    http://bhepple.freeshell.org

    Copyright (C) 2010 Bob Hepple <bhepple@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

/**
 * \file tba_test.c unit tests for tba.c
 *
 * To run from the source directory:
 *
 * libtool --mode=execute valgrind --leak-check=full ./tests/tba_test
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tba.h>

/* extern jmp_buf               NoMoreHeap; 
  - don't bother with exceptions for the test program: */

void *xcalloc (size_t nmemb, size_t size)
{
    void *retval = calloc(nmemb, size);
    /* Problem - no more heap - the following longjmp could take us
     * to a recovery routine that frees some stuff (maybe a buffer
     * alloc'd as a safety reserve) and then either exit safely or try
     * to continue, perhaps with a warning. */
    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void *xmalloc (size_t size)
{
    void *retval = malloc(size);
    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void *xrealloc(void *oldbuf, size_t size)
{
    void *retval = NULL;

    retval = realloc(oldbuf, size);

    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void xfree(void *buf)
{
    free(buf);
}

void printfArray(char *label, tba  s)
{
    int len = 0;

    if (s) {
        tba p = s;
        for (; *p; p++) len++;
        printf("%s: length = %d, maxlen = %d, curlen = %d\n", label,
               len, tba_get_maxlen(s), tba_get_len(s));
    }
}

void header(char *s, int error, int line)
{
    static int last_error = 0;
    if (error == last_error) {
        printf("ok\n");
    } else {
        last_error = error;
    }

    printf("*******************************\nline %d: %s:\n", line, s);
}

#define PRINT_CLUES                                                     \
    printfArray("a", a);                                                \
    printfArray("b", b);                                                \
    printfArray("c", c);                                                \
    printf("i = %d chunksize = %d\n", i, chunksize);                  \
    errors++;

/* pointer */
#define pEXPECT(expr)                                                   \
    if ( !(expr) ) {                                                    \
        printf("\nline %d: error in \"%s\" got NULL\n",                 \
               __LINE__, # expr);                                       \
        PRINT_CLUES;                                                    \
    }

/* null pointer */
#define npEXPECT(expr)                                                  \
    if ( (expr)) {                                                      \
        printf("\nline %d: error in \"%s\" expected NULL\n",            \
               __LINE__, # expr);                                       \
        PRINT_CLUES;                                                    \
    }

/* integer */
#define iEXPECT(expr,val)                                               \
    if ((value = (expr)) != (val)) {                                    \
        printf("\nline %d: error in \"%s\" got %d, expected %d\n",      \
               __LINE__, # expr, value, val);                           \
        PRINT_CLUES;                                                    \
    }

/* positive integer */
#define piEXPECT(expr)                                                  \
    if ((value = (expr)) <= 0) {                                        \
        printf("\nline %d: error in \"%s\" got %d, expected >0\n",      \
               __LINE__, # expr, value);                                \
        PRINT_CLUES;                                                    \
    }

/* non-positive integer ie <= 0 */
#define niEXPECT(expr)                                                  \
    if ((value = (expr)) > 0) {                                         \
        printf("\nline %d: error in \"%s\" got %d, expected <=0\n",      \
               __LINE__, # expr, value);                                \
        PRINT_CLUES;                                                    \
    }

/* boolean */
#define bEXPECT(expr)                                                   \
    if ( !(expr) ) {                                                    \
        printf("\nline %d: error in \"%s\"\n",                          \
               __LINE__, # expr);                                       \
        PRINT_CLUES;                                                    \
    }

#define MIN(x,y) ((x)<=(y) ? (x): (y))
#define MAX(x,y) ((x)>=(y) ? (x): (y))
#define TEST_STRING "Hello world"
#define SENTINEL1 0xdeadbeef
#define SENTINEL2 0xbeefdead
#define ORIG_SIZE 5
#define ORIG_WIDTH 20
#define NEW_SIZE 10
#define COPY_OFFSET 4000

struct payload_s {
    int sentinel1;
    char *text;
    int count;
    int sentinel2;
};

/* constant width payload */

/* callback frees anything pointed to in payload and returns payload */
static void *free_callback(void *payload, size_t index, void *caller_data)
{
    struct payload_s *pl = (struct payload_s *) payload;

    if (pl && pl->text) xfree(pl->text);
    return(payload);
}

/* assumes tba has done malloc - don't check so we get an abort on
 * first error! */
static void *create_callback(void *payload, size_t index, void *caller_data)
{
    void *retval = NULL;
    struct payload_s *pl = (struct payload_s *) payload;

    retval = payload;
    pl->sentinel1 = SENTINEL1;
    pl->text = strdup(TEST_STRING);
    pl->count = index;
    pl->sentinel2 = SENTINEL2;
    return(retval);
}

/* assumes tba has done malloc */
static void *copy_callback(void *dest, void *source, size_t index, 
                           void *caller_data)
{
    struct payload_s *dpl = NULL;
    struct payload_s *spl = NULL;
    void *retval = NULL;

    dpl = (struct payload_s *) dest;
    spl = (struct payload_s *) source;

    retval = dest;
    dpl->sentinel1 = spl->sentinel1;
    dpl->text = strdup(spl->text);
    dpl->count = spl->count + COPY_OFFSET;
    dpl->sentinel2 = spl->sentinel2;

    return(retval);
}

/* variable length payloads */

/* callback does free and returns NULL */
static void *v_free_callback(void *payload, size_t index, void *caller_data)
{
    struct payload_s *pl = (struct payload_s *) payload;

    if (pl && pl->text) xfree(pl->text);
    if (payload) xfree(payload);
    return(NULL);
}

/* callback does malloc because tba doesn't know the width */
static void *v_create_callback(void *payload, size_t index, void *caller_data)
{
    void *retval = NULL;
    struct payload_s *pl = NULL;

    pl = xmalloc(sizeof(struct payload_s) + index);
    retval = pl;

    if (pl) {
        pl->sentinel1 = SENTINEL1;
        pl->text = strdup(TEST_STRING);
        pl->count = index;
        pl->sentinel2 = SENTINEL2;
    }
    return(retval);
}

/* calls create_callback */
static void *v_copy_callback(void *dest, void *source, size_t index, 
                             void *caller_data)
{
    void *retval = NULL;

    if (!source) goto end;

    retval = v_create_callback(dest, index, caller_data);
    if (retval) {
        struct payload_s *dpl;
        struct payload_s *spl;

        dest = retval;
        dpl = (struct payload_s *) dest;
        spl = (struct payload_s *) source;
        if (dpl->text) xfree(dpl->text);
        dpl->sentinel1 = spl->sentinel1;
        dpl->text = strdup(spl->text);
        dpl->count = spl->count + 4000;
        dpl->sentinel2 = spl->sentinel2;
    }

end:
    return(retval);
}

int check_payload(struct payload_s *pl, int expected_count)
{
    int retval = 0;

    if (!pl) {
        printf("\nerror: NULL payload\n");
        goto end;
    }
    if (pl->sentinel1 != SENTINEL1) {
        printf("\nerror: bad sentinel1 in payload\n");
        goto end;
    }
    if (!pl->text) {
        printf("\nerror: NULL text in payload\n");
        goto end;
    }
    if (strcmp(pl->text, TEST_STRING)) {
        printf("\nerror: bad text in payload\n");
        goto end;
    }

    if (pl->count != expected_count) {
        printf("\nerror: bad index in payload: got %d expected %d\n", 
               pl->count,
               expected_count);
        goto end;
    }
    if (pl->sentinel2 != SENTINEL2) {
        printf("\nerror: bad sentinel2 in payload\n");
        goto end;
    }
    retval = 1;

end:
    return(retval);
}

int unittest(int chunksize)
{
    int errors = 0;
    tba a = NULL, b = NULL, c = NULL;
    int value;
    int i = 0;

#if 0
    char *p = NULL;
    int len;
    int pos;
#endif

    printf("\nRunning unittest with chunksize = %d\n", chunksize);

    tba_set_chunksize(chunksize);

    header("tba_new/free - constant width payload without callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 1]);
    npEXPECT(a[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_new/free - constant width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, &free_callback, 
                            &create_callback, &copy_callback, NULL));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 1]);
    npEXPECT(a[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    tba_free(&a);

    header("tba_new/free - variable width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, 0, &v_free_callback, 
                            &v_create_callback, &v_copy_callback, NULL));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 1]);
    npEXPECT(a[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    tba_free(&a);

    header("tba_get_maxlen", errors, __LINE__);
    iEXPECT(tba_get_maxlen(NULL), 0);

    header("tba_resize - constant width payload without callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_resize(&a, NEW_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < NEW_SIZE; i++) {
        pEXPECT(a[i]);
    }
    pEXPECT(tba_resize(&a, ORIG_SIZE));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 1]);
    npEXPECT(a[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);

    header("tba_resize - constant width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, &free_callback, 
                            &create_callback, &copy_callback, NULL));
    pEXPECT(tba_resize(&a, NEW_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < NEW_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    pEXPECT(tba_resize(&a, ORIG_SIZE));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 1]);
    npEXPECT(a[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_resize - variable width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, 0, &v_free_callback, &v_create_callback, 
                            &v_copy_callback, NULL));
    pEXPECT(tba_resize(&a, NEW_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < NEW_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    pEXPECT(tba_resize(&a, ORIG_SIZE));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 1]);
    npEXPECT(a[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - constant width payload without callbacks - insert at the start", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_insert(&a, 0, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    npEXPECT(a[NEW_SIZE]);
    i = 5; pEXPECT(a[i]);
    for (i = 0; i < NEW_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - constant width payload without callbacks - insert near start", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_insert(&a, 2, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < NEW_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - constant width payload without callbacks - insert at the end", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_insert(&a, -1, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < NEW_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - constant width payload without callbacks - insert near the end", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_insert(&a, ORIG_SIZE - 2, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < NEW_SIZE; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - constant width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, &free_callback, 
                            &create_callback, &copy_callback, NULL));
    pEXPECT(tba_insert(&a, 2, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < 2; i ++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    for (i = 2; i < 2 + NEW_SIZE - ORIG_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    for (i = 2 + NEW_SIZE - ORIG_SIZE; i < NEW_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], 
                              i - (NEW_SIZE - ORIG_SIZE)));
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - variable width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, 0, &v_free_callback, 
                            &v_create_callback, &v_copy_callback, NULL));
    pEXPECT(tba_insert(&a, 2, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[NEW_SIZE - 1]);
    npEXPECT(a[NEW_SIZE]);
    for (i = 0; i < 2; i ++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    for (i = 2; i < 2 + NEW_SIZE - ORIG_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], i));
    }
    for (i = 2 + NEW_SIZE - ORIG_SIZE; i < NEW_SIZE; i++) {
        bEXPECT(check_payload((struct payload_s *)a[i], 
                              i - (NEW_SIZE - ORIG_SIZE)));
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_insert - NULL array", errors, __LINE__);
    npEXPECT(tba_insert(&a, 2, NEW_SIZE - ORIG_SIZE));

    header("tba_delete - constant width payload without callbacks - delete at the end", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_delete(a, -1, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 2]);
    npEXPECT(a[ORIG_SIZE - 1]);
    for (i = 0; i < ORIG_SIZE - 1; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);
    npEXPECT(a);

    header("tba_delete - constant width payload without callbacks - delete at the start", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_delete(a, 0, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 2]);
    npEXPECT(a[ORIG_SIZE - 1]);
    for (i = 0; i < ORIG_SIZE - 1; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);

    header("tba_delete - constant width payload without callbacks - delete in the middle", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(tba_delete(a, 2, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    pEXPECT(a[0]);
    pEXPECT(a[ORIG_SIZE - 2]);
    npEXPECT(a[ORIG_SIZE - 1]);
    for (i = 0; i < ORIG_SIZE - 1; i++) {
        pEXPECT(a[i]);
    }
    tba_free(&a);

    header("tba_delete - constant width payload with callbacks - delete one in the middle", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, &free_callback, 
                            &create_callback, &copy_callback, NULL));
    pEXPECT(tba_delete(a, 2, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    npEXPECT(a[4]);
    npEXPECT(a[ORIG_SIZE]);
    bEXPECT(check_payload((struct payload_s *) a[0], 0));
    bEXPECT(check_payload((struct payload_s *) a[1], 1));
    bEXPECT(check_payload((struct payload_s *) a[2], 3));
    bEXPECT(check_payload((struct payload_s *) a[3], 4));
    tba_free(&a);

    header("tba_delete - constant width payload with callbacks - delete lots in the middle", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, &free_callback, 
                            &create_callback, &copy_callback, NULL));
    pEXPECT(tba_delete(a, 2, ORIG_SIZE * 2));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    bEXPECT(check_payload((struct payload_s *) a[0], 0));
    bEXPECT(check_payload((struct payload_s *) a[1], 1));
    npEXPECT(a[2]);
    npEXPECT(a[3]);
    npEXPECT(a[4]);
    npEXPECT(a[ORIG_SIZE]);
    tba_free(&a);

    header("tba_delete - variable width payload with callbacks - delete lots from the end", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, 0, &v_free_callback, 
                            &v_create_callback, &v_copy_callback, NULL));
    pEXPECT(tba_delete(a, -1, ORIG_SIZE * 2));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    npEXPECT(a[4]);
    npEXPECT(a[ORIG_SIZE]);
    bEXPECT(check_payload((struct payload_s *) a[0], 0));
    bEXPECT(check_payload((struct payload_s *) a[1], 1));
    bEXPECT(check_payload((struct payload_s *) a[2], 2));
    bEXPECT(check_payload((struct payload_s *) a[3], 3));
    tba_free(&a);

    header("tba_copy - constant width payload without callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    pEXPECT(b = tba_copy(a));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(b));
    pEXPECT(b[0]);
    pEXPECT(b[ORIG_SIZE - 1]);
    npEXPECT(b[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE - 1; i++) {
        pEXPECT(b[i]);
    }
    tba_free(&a);
    tba_free(&b);

    header("tba_copy - constant width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, free_callback, 
                            &create_callback, &copy_callback, NULL));
    pEXPECT(b = tba_copy(a));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(b));
    pEXPECT(b[0]);
    pEXPECT(b[ORIG_SIZE - 1]);
    npEXPECT(b[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE - 1; i++) {
        bEXPECT(check_payload((struct payload_s *) b[i], i + COPY_OFFSET));
    }
    tba_free(&a);
    tba_free(&b);

    header("tba_copy - variable width payload without callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, 0, NULL, NULL, NULL, NULL));
    pEXPECT(b = tba_copy(a)); /* will be shrunk to chunksize */
    for (i = 0; i < tba_get_maxlen(b); i++) npEXPECT(b[i]);
    tba_free(&a);
    tba_free(&b);

    header("tba_copy - variable width payload with callbacks", errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, -1, v_free_callback, 
                            &v_create_callback, &v_copy_callback, NULL));
    pEXPECT(b = tba_copy(a));
    npEXPECT(b[ORIG_SIZE]);
    for (i = 0; i < ORIG_SIZE - 1; i++) {
        bEXPECT(check_payload((struct payload_s *) b[i], i + COPY_OFFSET));
    }
    tba_free(&a);
    tba_free(&b);

    {
        int i1 = COPY_OFFSET;
        int i2 = i1 + 1;
        int i3 = i2 + 1;
        int *p;
        i = COPY_OFFSET;
        header("tba_push", errors, __LINE__);
        pEXPECT(a = tba_push(NULL, &i1));
        pEXPECT(tba_push(&a, &i2));
        pEXPECT(tba_push(&a, &i3));
        header("tba_get_last", errors, __LINE__);
        pEXPECT(p = (int *)tba_get_last(a));
        iEXPECT(*p, COPY_OFFSET + 2);
        header("tba_get_first", errors, __LINE__);
        pEXPECT(p = (int *)tba_get_first(a));
        iEXPECT(*p, COPY_OFFSET);
        header("tba_pop_last", errors, __LINE__);
        pEXPECT(p = (int *)tba_pop_last(a));
        iEXPECT(*p, COPY_OFFSET + 2);
        header("tba_pop_first", errors, __LINE__);
        pEXPECT(p = (int *)tba_pop_first(a));
        iEXPECT(*p, COPY_OFFSET);
        pEXPECT(p = (int *)tba_pop_first(a));
        iEXPECT(*p, COPY_OFFSET + 1);
        npEXPECT(p = (int *)tba_pop_first(a));
        
        tba_free(&a);
    }

    header("tba_put_index/get - NULL destination", errors, __LINE__);
    npEXPECT(tba_put_index(NULL, 1, NULL));
    pEXPECT(a = tba_put_index(&a, 0, &a));
    npEXPECT(tba_put_index(&a, -1, &a));
    iEXPECT(tba_get_len(a), 1);
    npEXPECT(tba_get_index(a, 0) - (void *)&a);
    header("tba_put_index/get - put beyond the end of the array", errors, 
           __LINE__);
    pEXPECT(a = tba_put_index(&a, NEW_SIZE - 1, &a));
    iEXPECT(tba_get_len(a), NEW_SIZE);
    for (i = 0; i < NEW_SIZE; i++) npEXPECT(tba_get_index(a, i) - a[i]);
    pEXPECT(b = tba_copy(a));
    for (i = 0; i < NEW_SIZE; i++) pEXPECT(b = tba_put_index(&b, i, a[i]));
    iEXPECT(tba_get_len(b), NEW_SIZE);
    for (i = 0; i < NEW_SIZE; i++) npEXPECT(tba_get_index(b, i) - a[i]);
    tba_free(&a);
    tba_free(&b);

    header("tba_put_index/get - constant width payload without callbacks", 
           errors, __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, NULL, NULL, NULL, NULL));
    npEXPECT(tba_put_index(&a, -1, &i));
    iEXPECT(tba_get_len(a), ORIG_SIZE);
    for (i = 0; i < ORIG_SIZE; i++) pEXPECT(tba_get_index(a, i));
    header("tba_put_index/get - put beyond the end of the array", errors, 
           __LINE__);
    pEXPECT(tba_put_index(&a, NEW_SIZE * 2, xmalloc(ORIG_WIDTH)));
    /* put a hole in it: */
    xfree(a[ORIG_SIZE]);
    a[ORIG_SIZE] = NULL;

    iEXPECT(tba_get_len(a), NEW_SIZE * 2 + 1);
    pEXPECT(b = tba_copy(a));
    iEXPECT(tba_get_len(b), NEW_SIZE * 2 + 1);
    tba_free(&a);
    tba_free(&b);

    header("tba_put_index/get - constant width payload with callbacks", errors, 
           __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, ORIG_WIDTH, free_callback, 
                            &create_callback, &copy_callback, NULL));
    npEXPECT(tba_put_index(&a, -1, &i));
    iEXPECT(tba_get_len(a), ORIG_SIZE);
    for (i = 0; i < ORIG_SIZE; i++) pEXPECT(tba_get_index(a, i));
    header("tba_put_index/get - put beyond the end of the array", errors, 
           __LINE__);
    pEXPECT(tba_put_index(&a, NEW_SIZE * 2, 
                        create_callback(xmalloc(ORIG_WIDTH),
                                        NEW_SIZE * 2 + 1, NULL)));
    iEXPECT(tba_get_len(a), NEW_SIZE * 2 + 1);
    pEXPECT(b = tba_copy(a));
    iEXPECT(tba_get_len(b), NEW_SIZE * 2 + 1);
    tba_free(&a);
    tba_free(&b);

    header("tba_copy - variable width payload without callbacks", errors, 
           __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, 0, NULL, NULL, NULL, NULL));
    npEXPECT(tba_put_index(&a, -1, &i));
    iEXPECT(tba_get_len(a), 0);
    for (i = 0; i < ORIG_SIZE; i++) npEXPECT(tba_get_index(a, i));
    header("tba_put_index/get - put beyond the end of the array", errors, 
           __LINE__);
    pEXPECT(tba_put_index(&a, NEW_SIZE * 2, &i));

    iEXPECT(tba_get_len(a), NEW_SIZE * 2 + 1);
    pEXPECT(b = tba_copy(a));
    iEXPECT(tba_get_len(b), NEW_SIZE * 2 + 1);
    tba_free(&a);
    tba_free(&b);

    header("tba_copy - variable width payload with callbacks", errors, 
           __LINE__);
    pEXPECT(a = tba_new(ORIG_SIZE, -1, v_free_callback, 
                            &v_create_callback, &v_copy_callback, NULL));
    npEXPECT(tba_put_index(&a, -1, &i));
    iEXPECT(tba_get_len(a), ORIG_SIZE);
    for (i = 0; i < ORIG_SIZE; i++) pEXPECT(tba_get_index(a, i));
    header("tba_put_index/get - put beyond the end of the array", errors, 
           __LINE__);
    pEXPECT(tba_put_index(&a, NEW_SIZE * 2, 
                        v_create_callback(NULL, NEW_SIZE * 2, NULL)));
    iEXPECT(tba_get_len(a), NEW_SIZE * 2 + 1);
    pEXPECT(b = tba_copy(a));
    iEXPECT(tba_get_len(b), NEW_SIZE * 2 + 1);
    tba_free(&a);
    tba_free(&b);


    header("unittest finished", errors, __LINE__);
    printf("unittest(%d): error count = %d\n", chunksize, errors);
    return(errors);
}

int main(int argc, char **argv)
{
    int errors = 0;
#if 0
    int value;
    tba a = NULL;
    tba b = NULL;
    tba c = NULL;
#endif

    setenv("TBARRAY_CHECK", "1", 2);

    errors += unittest(1);
    errors += unittest(23);
    errors += unittest(128);
    errors += unittest(4096);
    errors += unittest(40960);

#if 0
    header("tba_check", errors, __LINE__);
    pEXPECT(a = tba_new_str("asd"));
    memcpy(a, "mmm", 4); /* overwrite the first sentinel */
    npEXPECT(b = tba_strdup(a));
    free(a); a = NULL;
    free(b); b = NULL;

    {
        size_t physize;
        size_t *sentinel2;

        pEXPECT(a = tba_new_str("asd"));
        physize = ((a->maxlen + 1) / sizeof(size_t)) + 4;
        sentinel2 = (size_t *) a + physize - 1;

        memcpy(sentinel2, "mmm", 4); /* overwrite the second sentinel */
    }
    npEXPECT(b = tba_strdup(a));
    iEXPECT(tba_get_maxlen(a), -1);
    free(a); a = NULL;
    free(b); b = NULL;
#endif

    printf("\nTotal: %d error%c\n", errors, errors == 1? ' ': 's');
    return(errors);
}

